﻿/**
 * 事件集合
 * 处理通信的所有事件都在此处定义
 */
interface IEvents {
    /**
     * 收到文本消息事件
     * @param sendId 发送者 Id
     * @param message 收到的文本
     * @returns {} 
     */
    OnTextMessageReceived: (sendId: string, message: string) => void;
    /**
     * 发送用户信息成功事件
     * @param myId 如果 Id 与自己发送的的 Id 一样则表示服务器处理成功
     * @returns {} 
     */
    OnOwnUserInfoReceived: (myId: string) => void;
    /**
     * 用户上线事件
     * @param userInfo 用户信息
     * @returns {} 
     */
    OnNewUserConnected: (userInfo: IContactsInfo) => void;
    /**
     * 用户下线事件
     * @param userInfo 用户信息
     * @returns {} 
     */
    OnUserOffline: (userInfo: IContactsInfo) => void;
    /**
     * 收到为指定解析类型的消息
     * @param message 消息内容
     * @returns {} 
     */
    OrtherMessageReceived: (message: any) => void;
    /**
     * 关闭事件
     * @param e 事件的系统参数
     * @returns {} 
     */
    OnClosed: (e: CloseEvent) => void;
    /**
     * 发送用户信息发生错误
     * @returns {} 
     */
    SendUserInfoError: () => void;
    /**
     * 发送文本消息错误
     * @param info 
     * @returns {} 
     */
    SendTextMessageError: (info: string) => void;
    /**
     * 请求用户列表错误
     * @returns {} 
     */
    SendRequestUsersMessageError: () => void;
    /**
     * 收到广播消息
     * @param sendId 广播者 Id
     * @param message 广播内容
     * @returns {} 
     */
    BroadcastTextMessageReceived: (sendId: string, message: string) => void;
    /**
     * 更新联系人列表成功
     * @returns {} 
     */
    UpdateContactsComplete: () => void;
    /**
     * 浏览器错误
     * @returns {} 
     */
    BrowserError: () => void;
    /**
     * 打开通信组件成功
     * @param e 
     * @returns {} 
     */
    OnOpened: (e: Event) => void;
    /**
     * 通信组件发生错误
     * @param e 
     * @returns {} 
     */
    OnError: (e: Event) => void;
}

/**
 * 联系人信息
 */
interface IContactsInfo {
    /**
     * 标识
     */
    Id: string;
    /**
     * 名称
     */
    Name: string;
    /**
     * 密码
     */
    Password: string;
    /**
     * 登录时间
     */
    LoginTime: string | Date | number;
    /**
     * 网络终结点的文本表示形式
     */
    IpEndPoint: string;
    /**
     * 传送文件客户端
     */
    TransferFile: boolean;
    /**
     * Web 客户端
     */
    WebSocket: boolean;
    /**
     * Web 客户端 Key
     */
    WebSocketKey: string;
    /**
     * Web 客户端加密 Key
     */
    AcceptKey: string;
}

/**
 * 消息类型
 */
enum MessageTypes {
    /**
     * 文本类型消息
     */
    Text,
    /**
     * 文件类型消息
     */
    File,
    /**
     * 用户信息消息
     */
    UserInfo,
    /**
     * 自定义消息
     */
    Custom,
    /**
     * 连接成功通知消息
     */
    Connected,
    /**
     * 下线通知消息
     */
    Offline,
    /**
     * 请求联系人列表
     */
    RequestUsers,
    /**
     * 请求发送文件
     */
    RequestSendFile,
    /**
     * 同意接收文件
     */
    AgreeReceiveFile,
    /**
     * 广播消息
     */
    BroadcastText
}

/**
 * Web 通信错误
 */
enum WebCommunicationErrors {
    /**
     * 浏览器不支持通信
     */
    Nonsupport,
    /**
     * 未指定的消息类型
     */
    NotSpecified,
    /**
     * 非标准类型的消息
     */
    Atypia,
    /**
     * 未知类型的消息
     */
    Unknown
}

/**
 * 文件信息
 */
interface ITransferFileInfo {
    /**
     * 源文件名称
     */
    SourceFileName: string;
    /**
     * 保存文件名
     */
    FileName: string;
    /**
     * 请求
     */
    Request: boolean;
    /**
     * 同意
     */
    Agree: boolean;
    /**
     * 当前发送长度
     */
    CurrentSize: number;
    /**
     * 当前发送长度
     */
    LastSize: number;
    /**
     * 接收者 Id
     */
    ReceiveId: string;
    /**
     * 发送者 Id
     */
    SendId: string;
    /**
     * 发送者用户的 Id
     */
    SendUserId: string;
    /**
     * 接收者用户的 Id
     */
    ReceiveUserId: string;
    /**
     * 发送文件的客户端
     */
    Client: BaseCommunication;
    /**
     * 文件大小
     */
    Size: number;
    /**
     * 是否是发送端
     */
    IsSend: boolean;
    /**
     * 是否发送完毕
     */
    IsOver: boolean;
    /**
     * 发送结束时间
     */
    OverTime: Date;
    /**
     * 文件的哈希码
     */
    FileHashCode: string;
}

/**
 * 消息信息
 */
interface IMessageInfo {
    /**
     * 接收者标识
     */
    ReceiveId: string;
    /**
     * 发送者标识
     */
    SendId: string;
    /**
     * 消息长度
     */
    Length: number;
    /**
     * 消息类型
     */
    MessageType: MessageTypes;
    /**
     * 传送文件信息
     */
    TransferFileInfo: ITransferFileInfo;
}

/**
 * 基础通信框架
 */
class BaseCommunication {
    /**
     * 事件列表
     */
    Events: IEvents;
    /**
     * 通信客户端
     */
    Socket: WebSocket;
    /**
     * 联系人列表
     */
    Contacts: Array<IContactsInfo>;
    /**
     * 用户信息
     */
    UserInfo: IContactsInfo;
    /**
     * 服务器 Ip
     */
    Ip: string;
    /**
     * 服务器端口
     */
    Port: number;

    /**
     * 初始化基础通信组件
     * @param events 事件列表
     * @returns {} 
     */
    constructor(events: IEvents) {
        this.Events = events;
    }

    /**
     * 初始化通信组件连接状态
     * @param ip 服务器 Ip
     * @param port 服务器端口
     * @returns {} 
     */
    Initialize(ip: string, port: number) {
        this.Ip = ip;
        this.Port = port;

        // 判断浏览器是否支持 WebSocket
        if (typeof WebSocket == "undefined") {
            this.Events.BrowserError();
            return;
        }

        // 初始化 WebSocket 对象
        this.Socket = new WebSocket(`ws://${ip}:${port}`);

        // 绑定 Socket 连接成功事件
        this.Socket.onopen = (e) => {
            this.Events.OnOpened(e);
        }

        // 绑定 Socket 收到消息事件
        this.Socket.onmessage = (e) => {
            // 取出消息
            var messageData = e.data;
            // 判断消息是否问文本
            if (typeof e.data == "string") {
                // 取出消息信息的长度
                var lengthMsgInfo = Number(messageData.substring(0, 128));
                // 是否是标准消息
                if (!isNaN(lengthMsgInfo)) {
                    // 取出消息信息
                    var jsonMessageInfo: string = messageData.substring(128, 128 + lengthMsgInfo);
                    var messageInfo = <IMessageInfo>JSON.parse(jsonMessageInfo);
                    var jsonUserInfo: string;
                    var userInfo: IContactsInfo;
                    var textMessage: string;
                    // 解析消息
                    var contacts = this.Contacts;
                    switch (messageInfo.MessageType) {
                        case MessageTypes.Text:
                            // 文本消息处理
                            textMessage = messageData.substring(128 + lengthMsgInfo, 128 + lengthMsgInfo + messageInfo.Length);
                            this.Events.OnTextMessageReceived(messageInfo.SendId, textMessage);
                            break;
                        case MessageTypes.File:
                            // 文件消息处理
                            break;
                        case MessageTypes.UserInfo:
                            // 用户信息处理
                            this.Events.OnOwnUserInfoReceived(messageInfo.SendId);
                            break;
                        case MessageTypes.Custom:
                            // 自定义消息处理
                            break;
                        case MessageTypes.Connected:
                            // 联系人上线处理
                            jsonUserInfo = messageData.substring(128 + lengthMsgInfo, 128 + lengthMsgInfo + messageInfo.Length);
                            userInfo = <IContactsInfo>JSON.parse(jsonUserInfo);
                            if (userInfo.Id !== this.UserInfo.Id) {
                                contacts.push(userInfo);
                                this.Events.OnNewUserConnected(userInfo);
                            }
                            break;
                        case MessageTypes.Offline:
                            // 联系人下线处理
                            jsonUserInfo = messageData.substring(128 + lengthMsgInfo, 128 + lengthMsgInfo + messageInfo.Length);
                            userInfo = <IContactsInfo>JSON.parse(jsonUserInfo);
                            if (userInfo.Id !== this.UserInfo.Id) {
                                if (contacts.length !== 0) {
                                    let index = -1;
                                    for (var i in contacts) {
                                        if (contacts.hasOwnProperty(i)) {
                                            if (contacts[i].Id === userInfo.Id) {
                                                index = Number(i);
                                                break;
                                            }
                                        }
                                    }
                                    if (index !== -1) {
                                        contacts.splice(index, 1);
                                    }
                                    this.Events.OnUserOffline(userInfo);
                                }
                            }
                            break;
                        case MessageTypes.RequestUsers:
                            // 用户列表处理
                            jsonUserInfo = messageData.substring(128 + lengthMsgInfo, 128 + lengthMsgInfo + messageInfo.Length);
                            var users = <Array<IContactsInfo>>JSON.parse(jsonUserInfo);
                            this.Contacts = null;
                            this.Contacts = users;
                            this.Events.UpdateContactsComplete();
                            break;
                        case MessageTypes.RequestSendFile:
                            // 请求发送文件处理
                            break;
                        case MessageTypes.AgreeReceiveFile:
                            // 同意接收文件处理
                            break;
                        case MessageTypes.BroadcastText:
                            // 广播消息处理
                            textMessage = messageData.substring(128 + lengthMsgInfo, 128 + lengthMsgInfo + messageInfo.Length);
                            this.Events.BroadcastTextMessageReceived(messageInfo.SendId, textMessage);
                            break;
                        default:
                            // 未指定类型消息处理
                            this.Events.OrtherMessageReceived(messageData);
                            break;
                    }
                }
                else {
                    // 处理非标准类型消息
                    this.Events.OrtherMessageReceived(messageData);
                }
            }
            else {
                // 未知类型消息处理
                this.Events.OrtherMessageReceived(messageData);
            }
        }

        // 绑定 Socket 关闭事件
        this.Socket.onclose = (e) => {
            if (this.Socket != undefined && this.Socket != null && this.Socket.readyState === WebSocket.OPEN) {
                this.Socket.close();
            }
            this.Contacts = null;
            this.Contacts = new Array<IContactsInfo>();
            this.Events.OnClosed(e);
        }

        // 绑定 Socket 发生错误事件
        this.Socket.onerror = (e) => {
            if (this.Socket.readyState !== WebSocket.CLOSING) {
                this.Events.OnError(e);
            }
        }
    }

    /**
     * 发送当前用户信息进行登录
     * @param name 用户名
     * @param pwd 密码
     * @returns {} 
     */
    SendUserInfo(name: string, pwd: string): void {
        if (name == null || typeof name != "string" || name.trim() === "") {
            this.Events.SendUserInfoError();
            return;
        }
        if (this.Socket != undefined && this.Socket != null && this.Socket.readyState === WebSocket.OPEN) {
            this.UserInfo = null;
            const currDate: string | Date | number = `/Date(${Date.now()})/`;
            this.UserInfo = {
                Id: Guid.NewGuid().ToString("N").toLocaleUpperCase(),
                Name: name,
                Password: pwd,
                LoginTime: currDate
            } as IContactsInfo;
            
            const textJsonUserInfo = JSON.stringify(this.UserInfo);
            const messageInfo = {
                SendId: this.UserInfo.Id,
                Length: textJsonUserInfo.length,
                MessageType: MessageTypes.UserInfo
            } as IMessageInfo;
            const textJsonMsgInfo = JSON.stringify(messageInfo);
            const length = String(textJsonMsgInfo.length);
            const arrlength = Common.To128Length(length);
            this.Socket.send(arrlength + textJsonMsgInfo + textJsonUserInfo);
        } else {
            this.Events.SendUserInfoError();
        }
    }

    /**
     * 发送文本消息
     * @param id 接收者标识
     * @param value 消息内容
     * @returns {} 
     */
    SendTextMessage(id: string, value: string): void {
        if (value == null || value === "") {
            this.Events.SendTextMessageError("发送内容无效！");
            return;
        }
        if (this.Socket != null && this.Socket != undefined && this.Socket.readyState === WebSocket.OPEN) {
            const messageInfo = <IMessageInfo>{
                SendId: this.UserInfo.Id,
                ReceiveId: id,
                Length: value.length,
                MessageType: MessageTypes.Text
            };
            const textJsonMsgInfo = JSON.stringify(messageInfo);
            const length = String(textJsonMsgInfo.length);
            const arrlength = Common.To128Length(length);
            this.Socket.send(arrlength + textJsonMsgInfo + value);
        } else {
            this.Events.SendTextMessageError(null);
        }
    }

    /**
     * 根据标识获取用户信息
     * @param id 用户标识
     * @returns {} 
     */
    GetUserById(id: string): IContactsInfo {
        if (this.Socket != null && this.Socket != undefined && this.Socket.readyState === WebSocket.OPEN) {
            let user: IContactsInfo = undefined;
            for (let i = 0; i < this.Contacts.length; i++) {
                if (this.Contacts[i].Id === id) {
                    user = this.Contacts[i];
                    break;
                }
            }
            return user;
        }
        return null;
    }

    /**
     * 停止通信组件
     * @returns {} 
     */
    Stop(): void {
        if (this.Socket != null && this.Socket != undefined && this.Socket.readyState === WebSocket.OPEN) {
            this.Socket.close();
        }
    }

    /**
     * 请求联系人列表
     * @returns {} 
     */
    SendRequestUsersMessage(): void {
        if (this.Socket != undefined && this.Socket.readyState === WebSocket.OPEN) {
            const msgInfo = {
                "MessageType": MessageTypes.RequestUsers
            };
            const jsonMsgInfo = JSON.stringify(msgInfo);
            const length = String(jsonMsgInfo.length);
            const arrlength = Common.To128Length(length);
            this.Socket.send(arrlength + jsonMsgInfo);
        } else {
            this.Events.SendRequestUsersMessageError();
        }
    }

    /**
     * 广播文本消息
     * @param value 广播内容
     * @returns {} 
     */
    BroadcastTextMessage(value): void {
        if (value == null || value === "") {
            this.Events.SendTextMessageError("发送内容无效！");
            return;
        }
        if (this.Socket != undefined && this.Socket.readyState === WebSocket.OPEN) {
            const messageInfo = {
                "SendId": this.UserInfo.Id,
                "ReceiveId": null,
                "Length": value.length,
                "MessageType": 9
            };
            const textJsonMsgInfo = JSON.stringify(messageInfo);
            const length = String(textJsonMsgInfo.length);
            const arrlength = Common.To128Length(length);
            this.Socket.send(arrlength + textJsonMsgInfo + value);
        } else {
            this.Events.SendTextMessageError(null);
        }
    }
}

/**
 * 通用类
 */
class Common {
    /**
     * 姓的集合
     */
    private static firstName: string = "赵,钱,孙,李,周,吴,郑,王,冯,陈,褚,卫,蒋,沈,韩,杨,朱,秦,尤,许,何,吕,施,张,孔,曹,严,华,金,魏,陶,姜, 戚,谢,邹,喻,柏,水,窦,章,云,苏,潘,葛,奚,范,彭,郎,鲁,韦,昌,马,苗,凤,花,方,俞,任,袁,柳,丰,鲍,史,唐, 费,廉,岑,薛,雷,贺,倪,汤,滕,殷,罗,毕,郝,邬,安,常,乐,于,时,傅,皮,卞,齐,康,伍,余,元,卜,顾,孟,平,黄, 和,穆,萧,尹,姚,邵,湛,汪,祁,毛,禹,狄,米,贝,明,臧,计,伏,成,戴,谈,宋,茅,庞,熊,纪,舒,屈,项,祝,董,梁, 杜,阮,蓝,闵,席,季,麻,强,贾,路,娄,危,江,童,颜,郭,梅,盛,林,刁,钟,徐,丘,骆,高,夏,蔡,田,樊,胡,凌,霍, 虞,万,支,柯,昝,管,卢,莫,经,房,裘,缪,干,解,应,宗,丁,宣,贲,邓,郁,单,杭,洪,包,诸,左,石,崔,吉,钮,龚, 程,嵇,邢,滑,裴,陆,荣,翁,荀,羊,於,惠,甄,麴,家,封,芮,羿,储,靳,汲,邴,糜,松,井,段,富,巫,乌,焦,巴,弓, 牧,隗,山,谷,车,侯,宓,蓬,全,郗,班,仰,秋,仲,伊,宫,宁,仇,栾,暴,甘,钭,厉,戌,祖,武,符,刘,景,詹,束,龙, 叶,幸,司,韶,郜,黎,蓟,薄,印,宿,白,怀,蒲,邰,从,鄂,索,咸,籍,赖,卓,蔺,屠,蒙,池,乔,阴,郁,胥,能,苍,双, 闻,莘,党,翟,谭,贡,劳,逢,姬,申,扶,堵,冉,宰,郦,雍,郤,璩,桑,桂,濮,牛,寿,通,边,扈,燕,冀,郏,浦,尚,农, 温,别,庄,晏,柴,瞿,阎,充,慕,连,茹,习,宦,艾,鱼,容,向,古,易,慎,戈,廖,庾,终,暨,居,衡,步,都,耿,满,弘, 匡,国,文,寇,广,禄,阙,东,欧,殳,沃,利,蔚,越,菱,隆,师,巩,厍,聂,晃,勾,敖,融,冷,訾,辛,阚,那,简,饶,空, 曾,毋,沙,乜,养,鞠,须,丰,巢,关,蒯,相,查,后,荆,红,游,竺,权,逯,盖,益,桓,公,万俟,司马,上官,欧阳,夏侯,诸葛,闻人,东方,赫连,皇甫,尉迟,公羊,澹台,公冶,宗政,濮阳,淳于,单于,太叔,申屠,公孙,仲孙,轩辕,令狐,钟离,宇文,长孙,慕容,司徒,司空";
    /**
     * 名的集合
     */
    private static lastName: string = "努,迪,立,林,维,吐,丽,新,涛,米,亚,克,湘,明,白,玉,代,孜,霖,霞,加,永,卿,约,小,刚,光,峰,春,基,木,国,娜,晓,兰,阿,伟,英,元,音,拉,亮,玲,木,兴,成,尔,远,东,华,旭,迪,吉,高,翠,莉,云,华,军,荣,柱,科,生,昊,耀,汤,胜,坚,仁,学,荣,延,成,庆,音,初,杰,宪,雄,久,培,祥,胜,梅,顺,涛,西,库,康,温,校,信,志,图,艾,赛,潘,多,振,伟,继,福,柯,雷,田,也,勇,乾,其,买,姚,杜,关,陈,静,宁,春,马,德,水,梦,晶,精,瑶,朗,语,日,月,星,河,飘,渺,星,空,如,萍,棕,影,南,北";

    /**
     * 将文本长度不够128长度的补齐
     * @param value 需要补齐的文本
     * @returns {} 
     */
    static To128Length(value: string): string {
        const length = 128 - value.length;
        let result = value;
        if (length > 0 && length < 128) {
            for (let i = 0; i < length; i++) {
                result = "0" + result;
            }
        }
        return result;
    }

    /**
     * 判断字符串是否是 Ip 地址
     * @param value 
     * @returns {} 
     */
    static IsIp(value: string): boolean {
        const re = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
        return re.test(value);
    }

    /**
     * 获取一个随机姓名
     * @returns {} 
     */
    static GetRandomName(): string {
        const lengthName: number = Math.round(Math.random() * (4 - 2) + 2);
        const firstNames: Array<string> = this.firstName.split(",");
        const lastNames: Array<string> = this.lastName.split(",");
        let name: string;
        if (lengthName === 2) {
            name = firstNames[Math.floor(Math.random() * firstNames.length)] +
                lastNames[Math.floor(Math.random() * lastNames.length)];
        } else {
            name = firstNames[Math.floor(Math.random() * firstNames.length)] +
                lastNames[Math.floor(Math.random() * lastNames.length)] +
                lastNames[Math.floor(Math.random() * lastNames.length)];
        }
        return name;
    }

    /**
     * 格式化日期
     * @param date 日期
     * @param format 格式化类型
     * @returns {} 
     */
    static DateFormat(date?: Date, format?: string): string {
        if (date == null) {
            date = new Date();
        }
        if (format == null)
            return date.toString();
        let $1: string;
        const o = {
            "M+": date.getMonth() + 1,      // 月份，从0开始算  
            "d+": date.getDate(),           // 日期  
            "h+": date.getHours(),          // 小时  
            "m+": date.getMinutes(),        // 分钟  
            "s+": date.getSeconds(),        // 秒钟  
            // 季度 quarter  
            "q+": Math.floor((date.getMonth() + 3) / 3),
            "S": date.getMilliseconds() // 千秒  
        };
        let key;
        let value: string;

        if (/(y+)/.test(format)) {
            $1 = RegExp.$1,
                format = format.replace($1, String(date.getFullYear()).substr(4 - Number($1)));
        }

        for (key in o) {
            if (o.hasOwnProperty(key)) { // 如果没有指定该参数，则子字符串将延续到 stringvar 的最后。  
                if (new RegExp("(" + key + ")").test(format)) {
                    $1 = RegExp.$1,
                        value = String(o[key]),
                        value = $1.length === 1 ? value : ("00" + value).substr(value.length),
                        format = format.replace($1, value);
                }
            }
        }
        return format;
    }
}

/**
 * Guid 类
 */
class Guid {
    array: Array<string>;
    constructor(value: string) {
        this.array = new Array<string>();
        if (value != null && typeof value == "string") {
            this.InitializeByString(this.array, value);
        } else {
            this.InitializeByOther(this.array);
        }
    }

    /**
     * 使用指定字符串初始化 Guid
     * @param array 
     * @param mask 
     * @returns {} 
     */
    InitializeByString(array: Array<string>, mask: string) {
        mask = mask.replace(/\{|\(|\)|\}|-/g, "");
        mask = mask.toLowerCase();
        if (mask.length !== 32 || mask.search(/[^0-9,a-f]/i) !== -1) {
            this.InitializeByOther(array);
        } else {
            for (let i = 0; i < mask.length; i++) {
                array.push(mask[i]);
            }
        }
    }

    /**
     * 初始化 Guid
     * @param array 
     * @returns {} 
     */
    InitializeByOther(array: Array<string>) {
        let i = 32;
        while (i--) {
            array.push("0");
        }
    }

    /**
     * 获取 Guid 的字符串表示形式
     * @param format 指定格式
     * @returns {} 
     */
    ToString(format: string): string {
        if (typeof format == "string") {
            if (format === "N" || format === "D" || format === "B" || format === "P") {
                return this.ToStringWithFormat(this.array, format);
            } else {
                return this.ToStringWithFormat(this.array, "D");
            }
        } else {
            return this.ToStringWithFormat(this.array, "D");
        }
    }

    /**
     * 获取 Guid 的指定格式的字符串表示形式
     * @param array Guid
     * @param format 指定格式
     * @returns {} 
     */
    ToStringWithFormat(array: Array<string>, format: string): string {
        let str: string;
        switch (format) {
            case "N":
                return array.toString().replace(/,/g, "");
            case "D":
                str = array.slice(0, 8) + "-" + array.slice(8, 12) + "-" + array.slice(12, 16) + "-" + array.slice(16, 20) + "-" + array.slice(20, 32);
                str = str.replace(/,/g, "");
                return str;
            case "B":
                str = this.ToStringWithFormat(array, "D");
                str = "{" + str + "}";
                return str;
            case "P":
                str = this.ToStringWithFormat(array, "D");
                str = "(" + str + ")";
                return str;
            default:
                return new Guid(null).ToString("D");
        }
    }

    /**
     * 获取空的 Guid
     */
    static Empty: Guid = new Guid(null);

    /**
     * 获取一个新的 Guid
     * @returns {} 
     */
    static NewGuid(): Guid {
        let g = "";
        let i = 32;
        while (i--) {
            g += Math.floor(Math.random() * 16.0).toString(16);
        }
        return new Guid(g);
    }
}